package com.huanletao.tran.aspect;

import com.huanletao.spring.multdatasource.CustTransactional;
import com.huanletao.spring.multdatasource.SpringUtils;
import lombok.SneakyThrows;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Aspect    //该标签把LoggerAspect类声明为一个切面
@Order(1)  //设置切面的优先级：如果有多个切面，可通过设置优先级控制切面的执行顺序（数值越小，优先级越高）
@Component //该标签把LoggerAspect类放到IOC容器中
public class TransactionManagerAspect {

    Map<TransactionStatus,PlatformTransactionManager> transactionManagerMap = new ConcurrentHashMap<>(16);
    /**
     * 定义一个方法，用于声明切入点表达式，方法中一般不需要添加其他代码
     * 使用@Pointcut声明切入点表达式
     * 后面的通知直接使用方法名来引用当前的切点表达式；如果是其他类使用，加上包名即可
     */
    @Pointcut("@annotation(com.huanletao.spring.multdatasource.CustTransactional)")
    public void declearJoinPointExpression() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     */
    @SneakyThrows
   // @Before("declearJoinPointExpression()") //该标签声明次方法是一个前置通知：在目标方法开始之前执行
    public void beforeMethod(JoinPoint joinPoint) {
        Method proxyMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Method targetMethod = joinPoint.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
        CustTransactional annotation = targetMethod.getAnnotation(CustTransactional.class);
        if (Objects.nonNull(annotation)) {
            String[] transactionalGroup = annotation.value();
            for (String transactional : transactionalGroup) {
                PlatformTransactionManager transactionManager = SpringUtils.getBean(transactional);
                DefaultTransactionDefinition defintion = new DefaultTransactionDefinition();
                TransactionStatus transaction = transactionManager.getTransaction(defintion);
                transactionManagerMap.put(transaction,transactionManager);
            }
        }

        List<Object> args = Arrays.asList(joinPoint.getArgs());
        System.out.println("this method " + proxyMethod.getName() + " begin. param<" + args + ">");
    }
    /**
     * 拦截web层异常，记录异常日志，并返回友好信息到前端
     * 目前只拦截Exception，是否要拦截Error需再做考虑
     *
     * @param e 异常对象
     */
   // @AfterThrowing(pointcut = "declearJoinPointExpression()", throwing = "e")
    public void handleThrowing(Exception e) {
        //不需要再记录ServiceException，因为在service异常切面中已经记录过
        for (Map.Entry<TransactionStatus, PlatformTransactionManager> managerEntry : transactionManagerMap.entrySet()) {
            TransactionStatus transactionStatus = managerEntry.getKey();
            PlatformTransactionManager platformTransactionManager = managerEntry.getValue();
            platformTransactionManager.rollback(transactionStatus);
        }
        //执行完之后，清空
       // transactionManagerMap.clear();
    }
    /**
     * 后置通知（无论方法是否发生异常都会执行,所以访问不到方法的返回值）
     *
     * @param joinPoint
     */
   // @AfterReturning("declearJoinPointExpression()")
    public void afterMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        System.out.println("this method " + methodName + " end.");
        for (Map.Entry<TransactionStatus, PlatformTransactionManager> managerEntry : transactionManagerMap.entrySet()) {
            TransactionStatus transactionStatus = managerEntry.getKey();
            PlatformTransactionManager platformTransactionManager = managerEntry.getValue();
            platformTransactionManager.commit(transactionStatus);
        }
        //执行完之后，清空
        transactionManagerMap.clear();
    }

    @Around("declearJoinPointExpression()")
    public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
        String methodName = proceedingJoinPoint.getSignature().getName();
        Method proxyMethod = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        Method targetMethod = null;
        try {
            targetMethod = proceedingJoinPoint.getTarget().getClass().getMethod(proxyMethod.getName(), proxyMethod.getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        CustTransactional annotation = targetMethod.getAnnotation(CustTransactional.class);
        List<TransactionStatus> transactionStatuses = new ArrayList<>();
        if (Objects.nonNull(annotation)) {
            String[] transactionalGroup = annotation.value();
            for (String transactional : transactionalGroup) {
                PlatformTransactionManager transactionManager = SpringUtils.getBean(transactional);
                DefaultTransactionDefinition defintion = new DefaultTransactionDefinition();
                TransactionStatus transaction = transactionManager.getTransaction(defintion);
                transactionManagerMap.put(transaction,transactionManager);
            }
        }
        try {
            proceedingJoinPoint.proceed();
            for (Map.Entry<TransactionStatus, PlatformTransactionManager> managerEntry : transactionManagerMap.entrySet()) {
                TransactionStatus transactionStatus = managerEntry.getKey();
                PlatformTransactionManager platformTransactionManager = managerEntry.getValue();
                platformTransactionManager.commit(transactionStatus);
            }
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            for (Map.Entry<TransactionStatus, PlatformTransactionManager> managerEntry : transactionManagerMap.entrySet()) {
                TransactionStatus transactionStatus = managerEntry.getKey();
                PlatformTransactionManager platformTransactionManager = managerEntry.getValue();
                platformTransactionManager.rollback(transactionStatus);
            }
        }
        //执行完之后，清空
        transactionManagerMap.clear();
    }
}